Imports Microsoft.VisualBasic
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Xml
Imports System.Net
Imports System.ComponentModel
Imports System.Runtime.Serialization
Imports System.Security.Permissions
Imports System.Globalization
Imports C4F.DevKit.WebServices.Datasets

Namespace C4F.DevKit.WebServices
	''' <summary>
	''' Exposes search functionality on CraigsList.org
	''' </summary>
	<ToolboxItem(True)> _
	Public Class CraigsList
		Inherits C4FWebServicesBase
		#Region "Public Functions"
		''' <summary>
		''' Searches for the specified item from specified city
		''' </summary>
		''' <param name="city">Name of the city</param>
		''' <param name="itemName">Name of the item to be searched</param>
		''' <returns>List of items</returns>
		''' <exception cref="ArgumentException">The city is not specified</exception>
		''' <exception cref="CityNotSupportedException">An invalid city was entered as search parameter</exception>
		Public Function Search(ByVal city As String, ByVal itemName As String) As List(Of CraigsListItem)
			'if city not specified throw the exception
			If String.IsNullOrEmpty(city) Then
                Throw New ArgumentException(String.Format(CultureInfo.InvariantCulture, My.Resources.ErrorSpecifyArgument, "city"))
			End If

			city = city.Replace(" ", String.Empty)
			Dim requestUrl As String = "http://" & city & ".craigslist.org/search/sss?query=" & itemName & "&format=rss"

			Dim responseData As String = String.Empty

			Try
				' Get the responses
				responseData = MyBase.GetQueryResponse(requestUrl)
			Catch webEx As WebException
				' Check if server threw 502 error. This means invalid city was entered as search parameter
                If webEx.Message.Contains("The remote") Then
                    Throw New CityNotSupportedException(My.Resources.CityNotSupported, city, webEx)
                End If

				' Don't handle other web exceptions
				Throw
			End Try

			' Parse the data and return results
			Return CraigsList.ParseResponseXml(responseData)
		End Function

		''' <summary>
		''' Searches for the specified item from specified city and return the result in a dataset
		''' </summary>
		''' <param name="city">Name of the city</param>
		''' <param name="itemName">Name of the item to be searched</param>
		''' <returns>Dataset containing the list of items</returns>
		''' <exception cref="ArgumentException">The city is not specified</exception>
		''' <exception cref="CityNotSupportedException">An invalid city was entered as search parameter</exception>
		Public Function Search_DS(ByVal city As String, ByVal itemName As String) As DS_CraigsList
			Dim items As List(Of CraigsListItem) = Search(city, itemName)
			Dim dataset As DS_CraigsList = New DS_CraigsList()
			For Each item As CraigsListItem In items
				dataset.CraigsItem.AddCraigsItemRow(item.Title, item.Url, item.Date, item.Description, item.Language)
			Next item
			Return dataset
		End Function
		#End Region

		#Region "Private Functions"
		''' <summary>
		''' Parses responseData and populates CraigsListItem object list
		''' </summary>
		''' <param name="responseData">Search response data</param>
		''' <returns>List of CraigsList items of search result</returns>
		Private Shared Function ParseResponseXml(ByVal responseData As String) As List(Of CraigsListItem)
			Dim itemList As List(Of CraigsListItem) = New List(Of CraigsListItem)()
			Dim xmlDocument As XmlDocument = New XmlDocument()
			Dim nsmanager As XmlNamespaceManager = New XmlNamespaceManager(xmlDocument.NameTable)
			nsmanager.AddNamespace("rdf", "http://purl.org/rss/1.0/")
			nsmanager.AddNamespace("taxo", "http://purl.org/rss/1.0/modules/taxonomy/")
			nsmanager.AddNamespace("dc", "http://purl.org/dc/elements/1.1/")
			nsmanager.AddNamespace("syn", "http://purl.org/rss/1.0/modules/syndication/")
			nsmanager.AddNamespace("admin", "http://webns.net/mvcb/")
			xmlDocument.LoadXml(responseData)

			' Retrieve item nodes
			Dim itemNodes As XmlNodeList = xmlDocument.DocumentElement.SelectNodes("rdf:item", nsmanager)

			If itemNodes.Count = 0 Then
				Return itemList
			End If

			Dim cItem As CraigsListItem = Nothing
			For Each itemNode As XmlNode In itemNodes
				cItem = New CraigsListItem()
				'Populate cItem object
				If itemNode.SelectSingleNode("rdf:title", nsmanager) Is Nothing Then
					cItem.Title = Nothing
				Else
					cItem.Title = itemNode.SelectSingleNode("rdf:title", nsmanager).InnerText
				End If
				If itemNode.SelectSingleNode("rdf:link", nsmanager) Is Nothing Then
					cItem.Url = Nothing
				Else
					cItem.Url = itemNode.SelectSingleNode("rdf:link", nsmanager).InnerText
				End If
				If itemNode.SelectSingleNode("rdf:description", nsmanager) Is Nothing Then
					cItem.Description = Nothing
				Else
					cItem.Description = itemNode.SelectSingleNode("rdf:description", nsmanager).InnerText
				End If
				If itemNode.SelectSingleNode("dc:language", nsmanager) Is Nothing Then
					cItem.Language = Nothing
				Else
					cItem.Language = itemNode.SelectSingleNode("dc:language", nsmanager).InnerText
				End If
				If Not itemNode.SelectSingleNode("dc:date", nsmanager) Is Nothing Then
					cItem.Date = DateTime.Parse(itemNode.SelectSingleNode("dc:date", nsmanager).InnerText, CultureInfo.InvariantCulture)
				End If

				itemList.Add(cItem)
			Next itemNode
			Return itemList
		End Function
		#End Region
	End Class

	''' <summary>
	''' This class is used to store information related to CraigsList items.
	''' </summary>
	Public Class CraigsListItem
		#Region "Private Members"
		''' <summary>
		''' Title of the item
		''' </summary>
		Private title_Renamed As String
		''' <summary>
		''' URL with the details of the story
		''' </summary>
		Private url_Renamed As String
		''' <summary>
		''' Submit date
		''' </summary>
		Private date_Renamed As DateTime
		''' <summary>
		''' Item description
		''' </summary>
		Private description_Renamed As String
		''' <summary>
		''' Language
		''' </summary>
		Private language_Renamed As String
		#End Region

		#Region "Public Properties"
		''' <summary>
		''' Title of the item
		''' </summary>
		Public Property Title() As String
			Get
				Return Me.title_Renamed
			End Get
			Set(ByVal value As String)
				Me.title_Renamed = value
			End Set
		End Property
		''' <summary>
		''' URL with the details of the story
		''' </summary>
		Public Property Url() As String
			Get
				Return Me.url_Renamed
			End Get
			Set(ByVal value As String)
				Me.url_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Submit date
		''' </summary>
		Public Property [Date]() As DateTime
			Get
				Return Me.date_Renamed
			End Get
			Set(ByVal value As DateTime)
				Me.date_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Item description
		''' </summary>
		Public Property Description() As String
			Get
				Return Me.description_Renamed
			End Get
			Set(ByVal value As String)
				Me.description_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Language
		''' </summary>
		Public Property Language() As String
			Get
				Return Me.language_Renamed
			End Get
			Set(ByVal value As String)
				Me.language_Renamed = value
			End Set
		End Property
		#End Region
	End Class

	''' <summary>
	''' This exception is used to notify user about invalid city input.
	''' </summary>
	<Global.System.Serializable> _
	Public Class CityNotSupportedException
		Inherits Exception
		Private cityName_Renamed As String
		''' <summary>
		''' Name of city provided as search parameter
		''' </summary>
		Public Property CityName() As String
			Get
				Return Me.cityName_Renamed
			End Get
			Set(ByVal value As String)
				Me.cityName_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Initializes a new object of CityNotSupportedException class
		''' </summary>
		Public Sub New()
		End Sub
		''' <summary>
		''' Initializes a new object of CityNotSupportedException class
		''' </summary>
		''' <param name="message">The message that describes the error</param>
		Public Sub New(ByVal message As String)
			MyBase.New(message)
		End Sub
		''' <summary>
		''' Initializes a new object of CityNotSupportedException class
		''' </summary>
		''' <param name="message">The message that describes the error</param>
		''' <param name="innerException">The exception that is the cause of the current exception</param>
		Public Sub New(ByVal message As String, ByVal innerException As Exception)
			MyBase.New(message, innerException)
		End Sub
		''' <summary>
		''' Initializes a new object of CityNotSupportedException class
		''' </summary>
		''' <param name="message">The message that describes the error</param>
		''' <param name="cityName">Name of the city</param>
        Public Sub New(ByVal message As String, ByVal cityName As String)
            MyBase.New(message)
            Me.CityName = cityName
        End Sub
		''' <summary>
		''' Initializes a new object of CityNotSupportedException class
		''' </summary>
		''' <param name="message">The message that describes the error</param>
		''' <param name="cityName">Name of the city</param>
		''' <param name="innerException">The exception that is the cause of the current exception</param>
        Public Sub New(ByVal message As String, ByVal cityName As String, ByVal innerException As Exception)
            MyBase.New(message, innerException)
            Me.CityName = cityName
        End Sub
		''' <summary>
		''' Initializes a new object of YouTubeException class
		''' </summary>
		''' <param name="info">The System.Runtime.Serialization.SerializationInfo that holds the serialized
		''' object data about the exception being thrown.</param>
		''' <param name="context">The System.Runtime.Serialization.StreamingContext that contains contextual
		''' information about the source or destination.</param>
		Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
			MyBase.New(info, context)
				Me.CityName = info.GetString("cityName")
		End Sub
		''' <summary>
		''' Sets the System.Runtime.Serialization.SerializationInfo
		''' with information about the exception.
		''' </summary>
		''' <param name="info">The System.Runtime.Serialization.SerializationInfo that holds the serialized
		''' object data about the exception being thrown.</param>
		''' <param name="context">The System.Runtime.Serialization.StreamingContext that contains contextual
		''' information about the source or destination.</param>
		<SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter := True)> _
		Public Overrides Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
			info.AddValue("cityName", Me.CityName)
			MyBase.GetObjectData(info, context)
		End Sub
	End Class
End Namespace